/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.msu.client.discovery;

import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import io.iec.edp.caf.datasource.CAFDataSourceSelector;
import io.iec.edp.caf.msu.api.client.ServiceDiscovery;
import io.iec.edp.caf.msu.api.entity.GspAppServerInstance;
import io.iec.edp.caf.msu.api.entity.ServiceUnitInfo;
import io.iec.edp.caf.msu.client.exception.ServiceUnitNotFoundException;
import io.iec.edp.caf.msu.client.health.DbHealthSetting;
import io.iec.edp.caf.msu.common.domain.converter.GspAppConverter;
import io.iec.edp.caf.msu.common.domain.entity.GspAppServerEntity;
import io.iec.edp.caf.msu.common.domain.entity.GspSuEntity;
import io.iec.edp.caf.msu.common.domain.repository.AppServerRepository;
import io.iec.edp.caf.msu.common.domain.repository.SuRepository;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

/**
 * 服务中心-服务发现（DB版）
 * 返回一个健康的实例
 *
 * @date 2023-01-30
 */
@Slf4j
public class DbDiscoveryImpl implements ServiceDiscovery {

    private AppServerRepository appRepo;
    private SuRepository suRepo;

    public DbDiscoveryImpl(AppServerRepository appRepo, SuRepository suRepo) {
        this.appRepo = appRepo;
        this.suRepo = suRepo;
    }

    @Override
    public String discover(String serviceUnitName, HashMap<String, String> eventContext) {
        //根据su查找所有app实例
        List<GspAppServerInstance> apps = findAppsBySu(serviceUnitName);

        //随机获取实例
        //apps = apps.stream().filter(GspAppServerInstance::getHealthy).collect(Collectors.toList());   过滤健康实例
        GspAppServerInstance instance = selectService(apps);
        String url = instance.getAppUrl();
        String basepath = instance.getBasePath();

        if (url == null || url.length() == 0) {
            log.error("ServiceCenter(DataBase) Can't Find Instance by su {}", serviceUnitName);
            throw new ServiceUnitNotFoundException("ServiceCenter(DataBase) Current su " + serviceUnitName + " is not available");
        }

        return basepath != null && basepath.length() > 0 ? url + basepath : url;
    }

    @Override
    public List<String> discoverAll(String serviceUnitName, HashMap<String, String> eventContext) {
        //根据su查找所有app实例
        List<GspAppServerInstance> apps = findAppsBySu(serviceUnitName);

        //所有实例的url
        List<String> urls = new ArrayList<>();
        apps.forEach(x -> {
            if (x.getAppUrl() != null && x.getAppUrl().length() > 0) {
                urls.add(x.getBasePath() != null && x.getBasePath().length() > 0 ? x.getAppUrl() + x.getBasePath() : x.getAppUrl());
            }
        });
        return urls;
    }

    /**
     * 访问主库,获取GSPSuInstance表的所有数据并去重
     */
    @Override
    public List<ServiceUnitInfo> getEnabledServiceUnitInfo() {
        List<ServiceUnitInfo> infos = null;

        try {
            //设置主库
            CAFDataSourceSelector.selectMaster();

            //读取GSPSuInstance
            List<GspSuEntity> allSuEntity = this.suRepo.findAll();
            if (allSuEntity.size() > 0) {
                infos = new ArrayList<>();
                for (GspSuEntity entity : allSuEntity) {
                    //去重加入
                    if (infos.stream().noneMatch(x -> x.getName().equals(entity.getSu()))) {
                        ServiceUnitInfo info = new ServiceUnitInfo();
                        info.setName(entity.getSu());
                        info.setServiceUnitDes(entity.getSu());
                        infos.add(info);
                    }
                }
            }
        } finally {
            //重置数据库信息
            CAFDataSourceSelector.reset();
        }
        return infos;
    }

    /**
     * 根据su获取所有实例
     *
     * @param su 服务单元编号
     * @return 所有实例
     */
    private List<GspAppServerInstance> findAppsBySu(String su) {
        List<GspAppServerInstance> apps = new ArrayList<>();

        try {
            //设置主库
            CAFDataSourceSelector.selectMaster();

            List<GspAppServerEntity> entitys = this.appRepo.findBySuName(su.toLowerCase());
            if (entitys == null || entitys.size() == 0) {
                entitys = this.appRepo.findBySuName(su);
            }

            for (GspAppServerEntity entity : entitys) {
                apps.add(GspAppConverter.convertToInstance(entity));
            }
        } catch (Throwable e) {
            throw e;
        } finally {
            //重置数据库信息
            CAFDataSourceSelector.reset();
        }

        //实例数量为0
        if (apps.size() == 0) {
            log.error("ServiceCenter(DataBase) Can't Find App by su {}", su);
            throw new ServiceUnitNotFoundException("ServiceCenter(DataBase) Current su " + su + " is not available");
        }

        return apps;
    }

    /**
     * 随机选择app实例
     *
     * @param apps app实例列表
     * @return 随机app实例
     */
    private GspAppServerInstance selectService(List<GspAppServerInstance> apps) {
        if (apps == null || apps.size() == 0)
            return null;

        Date currentTimestamp = this.appRepo.getCurrentTimestamp();
        int beatPeriod = SpringBeanUtils.getBean(DbHealthSetting.class).getBeatPeriod();
        List<GspAppServerInstance> latest = new ArrayList<>();
        apps.forEach(app -> {
            if ((currentTimestamp.getTime() - Date.from(app.getBeatTime().toInstant()).getTime()) / 1000 <= beatPeriod) {
                latest.add(app);
            }
        });

        if (latest.size() > 0) {
            int index = (int) (Math.random() * latest.size());
            return latest.get(index);
        }

        int index = (int) (Math.random() * apps.size());

        return apps.get(index);
    }

}
